<?php
//全局bootstrap事件
use App\Toolys\CustomHttpException;
use App\Toolys\IpList;
use EasySwoole\Component\Di;
use App\Toolys\InterfaceData;
use EasySwoole\Kafka\Config\ProducerConfig;
use EasySwoole\Kafka\Kafka;

date_default_timezone_set('Asia/Shanghai');

/**  发送fastCacheQUEUE任务
 * @param string $queueName
 * @param string $classPath 处理的class路径
 * @param string $action 执行的class的方法
 * @param array $data function(arg1,arg2,arg3) = [1,2,3]
 * @param int $seconds 延时（秒）
 * @return int|null
 */
function _fastCacheQueue(string $queueName, string $classPath, string $action, array $data, int $seconds = 0): ?int
{
    /* 数据发放
     * _fastCacheQueue("default", "\App\Toolys\InterfaceData", "test", [1,2,3]);
     */
    $obj = json_encode(arrayTransitionObject([
        "class"=>$classPath,
        "action" => $action,
        "data" => $data
    ]));
    $job = new \EasySwoole\FastCache\Job();
    $job->setQueue($queueName);
    $job->setData($obj);
    if ($seconds > 0) {
        $job->setDelay($seconds);
    }
    return \EasySwoole\FastCache\Cache::getInstance()->putJob($job);
}


/** 发放kafka任务
 * @param string $topic
 * @param string $classPath 处理的class路径
 * @param string $action 执行的class的方法
 * @param array $data function(arg1,arg2,arg3) = [1,2,3]
 * @param string $key
 * @throws \EasySwoole\Kafka\Exception\Config
 * @throws \EasySwoole\Kafka\Exception\Exception
 * @throws \EasySwoole\Kafka\Exception\InvalidRecordInSet
 * *    author 韩天尊
 */
function _kafkaQueue(string $topic, string $classPath, string $action, array $data, string $key=""){
    /** 数据投放
     *  _kafkaQueue("default", "\App\Toolys\InterfaceData", "test", [1,2,3]);
     */

    $instance = \EasySwoole\EasySwoole\Config::getInstance();//配置获取
    $metadataBrokerList = $instance->getConf("KAFKA.url");
    $version = $instance->getConf("KAFKA.version");

    $config = new ProducerConfig();
    $config->setMetadataBrokerList($metadataBrokerList);
    $config->setBrokerVersion($version);
    $config->setRequiredAck(1);
    //$config->setTimeout();
    //$config->setRequestTimeout();
    $kafka = new Kafka($config);
    $kafka->producer()->send([
        [
            'topic' => $topic,
            'value' => json_encode([
                'data'=>$data,
                "class"=>$classPath,
                "action"=>$action
            ]),
            'key'=>$key
        ],
    ]);
}


/** toArray 对象转数组
 * @param $obj
 * @return array
 */
function objectTransitionArray($obj): array
{
    $obj = (array)$obj;
    foreach ($obj as $k => $v) {
        if (is_object($v) || is_array($v)) {
            $obj[$k] = (array)objectTransitionArray($v);
        }
    }
    return $obj;
}
/**  数组转对象
 * @param Array $array
 * @return Object
 * @author Quan
 */
function arrayTransitionObject(array $arr):object
{
   /* var_dump($arr);*/
    foreach ($arr as $k => $v) {
        if (is_array($v) || is_object($v)) {
            $arr[$k] = (object)arrayTransitionObject($v);
        }
    }
    return (object)$arr;
}


/** redis 统一前缀区分
 * @param string $key
 * @return string redis key
 */
function key_prefix(string $key)
{
    return (\EasySwoole\EasySwoole\Config::getInstance()->getConf("REDIS.key_prefix")) . $key;
}

/** redis 客户端
 * @param callable $callable
 * @param string $connectionName
 * @return mixed|null
 */
function _sealRedis(callable $callable, string $connectionName = "default")
{
    //_sealRedis(function (\EasySwoole\Redis\Redis $redis){});
    return \EasySwoole\RedisPool\RedisPool::invoke(
        function (\EasySwoole\Redis\Redis $redis) use ($callable) {
            return $callable($redis);
        }, $connectionName);
}

/** mysql 客户端
 * @param callable $callable
 * @param string $connectionName
 * @return mixed|void
 * @throws Throwable
 * @throws \EasySwoole\ORM\Exception\Exception
 * @throws \EasySwoole\Pool\Exception\PoolEmpty
 */
function _sealMysql(callable $callable, string $connectionName = "default")
{
    //_sealMysql(function($client){$testModel = Model::invoke($client);});
    return \EasySwoole\ORM\DbManager::getInstance()->invoke(
        function (\EasySwoole\ORM\Db\ClientInterface $client) use ($callable, $connectionName) {
            return $callable($client);
        }, $connectionName);
}

/** 穿透 --
 * @param array $ActionCommand
 * @param callable $dbDataCallable
 * @param bool $is_decode //是否需要 json序列化
 * @return mixed|null
 * @throws Throwable
 * @throws \EasySwoole\ORM\Exception\Exception
 * @throws \EasySwoole\Pool\Exception\PoolEmpty
 */
function penetrate(array $actionCommand, callable $dbDataCallable, bool $is_decode = false, $time=0)
{
    /*[
        "read"=> "get",
        "write"=> "set",
        "rawCommand"=> [key_prefix("app_getConfig")]
    ]
    [
        "read"=> "hGet",
        "write"=> "hSet",
        "rawCommand"=> [key_prefix("app_getConfig"),"field"]
    ]*/
    $commandObj = arrayTransitionObject($actionCommand);
    $data = \EasySwoole\RedisPool\RedisPool::invoke(
        function (\EasySwoole\Redis\Redis $redis) use($commandObj){
            array_unshift($commandObj->rawCommand, $commandObj->read);
            return $redis->rawCommand($commandObj->rawCommand);
        }, "default");
    if (empty($data)){
        $dbData = $dbDataCallable();
        $data = $is_decode ? json_encode($dbData) : $dbData;
        if (!empty($data)) {
            $commandObj->rawCommand[0] = $commandObj->write;
            $writeCommand = $commandObj->rawCommand;
            array_push($writeCommand, $data);
            //print_r($writeCommand);echo PHP_EOL;
            \EasySwoole\RedisPool\RedisPool::invoke(
                function (\EasySwoole\Redis\Redis $redis) use($writeCommand, $time){
                    $redis->rawCommand($writeCommand);
                    if ($time>0){//为了兼容set的时间
                        $redis->rawCommand(["expire",$writeCommand[1], $time]);
                    }
                }, "default");
        }
    }/*else{
        print_r($data);echo PHP_EOL;
        print_r("REDIS => read: {$commandObj->read}");echo PHP_EOL;
    }*/
    return $is_decode ? json_decode($data) : $data;
}

/** 全文搜索引擎初始化
 * @return \EasySwoole\ElasticSearch\ElasticSearch
 * @author 韩天尊
 * @date 2022-03-15 9:10
 */
function elasticSearchInti():\EasySwoole\ElasticSearch\ElasticSearch
{
    $config = new \EasySwoole\ElasticSearch\Config(
        \EasySwoole\EasySwoole\Config::getInstance()->getConf("ELASTICSEARCH")
    );
    return (new \EasySwoole\ElasticSearch\ElasticSearch($config));
}

/**
 * 全局setNx拦截（fastCache）
 */
\EasySwoole\Component\Di::getInstance()->set('fastCache_SetNx', function (string $keyName, int $seconds=6, $value="SetNx"):bool{
    if (!empty(\EasySwoole\FastCache\Cache::getInstance()->get($keyName))){return false;}
    if (!\EasySwoole\FastCache\Cache::getInstance()->set($keyName, $value, $seconds)){return false; }
    return true;
});

/**
 * 删除setNx key（fastCache）
 */
\EasySwoole\Component\Di::getInstance()->set('fastCache_DelNx', function (string $redisKeyString){
    if (!empty(\EasySwoole\FastCache\Cache::getInstance()->get($redisKeyString))){
        $del = \EasySwoole\FastCache\Cache::getInstance()->unset($redisKeyString);
        /*if ($del){
            $timer = date("Y-m-d H:i:s");
            echo "---------- Del lock KeyString: {$redisKeyString} -> {$timer} \n";
        }*/
    }
});

/** jwt加密
 * @param $id
 * @param array $otherInfo  用户其它信息
 * @param array $serverInfo 服务端信息
 * @param string $token_tag token应用端标识
 * @param int $timer        过期时间 (time()+)
 * @return string
 * @author 韩天尊
 * @date 2022-03-22 9:16
 */
function jwtCreate_toString($id, array $otherInfo=[], array $serverInfo=[], string $token_tag="APP", int $timer=8640000): string
{
    $time = time();
    $jti = \EasySwoole\Utility\Random::character(42);
    $jwtOrigin = \EasySwoole\EasySwoole\Config::getInstance()->getConf("APP_DATA.JwtOrigin")[$token_tag];
    $jwtObject = \EasySwoole\Jwt\Jwt::getInstance()
        ->setSecretKey($jwtOrigin)->publish();
    $jwtObject->setAlg('HMACSHA256');
    //$jwtObject->setAud('');
    $jwtObject->setExp($time + $timer);
    $jwtObject->setIat($time);
    $jwtObject->setIss('-[95&27_$_51),}');
    $jwtObject->setJti($jti);
    $jwtObject->setNbf($time+60*5); //在此之前不可用
    //$jwtObject->setSub(''); //主题
    $jwtObject->setData([
        "objectInfo"=>[
            "id"=> $id,
            "otherInfo"=> $otherInfo,
        ],
        "serverInfo"=> $serverInfo
    ]); //自定义数据
    $token = $jwtObject->__toString();//最终生成的token

    //如果后期加token控制可放redis [uid=>$jtis] 验证时解出jti对比
    $multipleDevices = \EasySwoole\EasySwoole\Config::getInstance()->getConf("APP_DATA.MultipleDevices");
    _sealRedis(function (\EasySwoole\Redis\Redis $redis) use ($token_tag, $id, $jti, $multipleDevices){
        $key = "token-{$token_tag}";
        $oldJtiS = json_decode($redis->hGet($key, $id), 1);
        if (empty($oldJtiS) || $multipleDevices===false){
            $oldJtiS = [];
        }
        array_push($oldJtiS, $jti);
        $redis->hSet($key, $id, json_encode($oldJtiS));
    });
    return $token;
}

/**
 * jwt解密
 * @throws Exception
 */
function verifyToken(string $token, string $token_tag="APP"):InterfaceData{
    $interfaceData = new InterfaceData();
    try {
        $jwtOrigin = \EasySwoole\EasySwoole\Config::getInstance()->getConf("APP_DATA.JwtOrigin")[$token_tag];
        $jwtObject = \EasySwoole\Jwt\Jwt::getInstance()->setSecretKey($jwtOrigin)->decode($token);
        switch ($jwtObject->getStatus()) {
            case  1:
                $interfaceData->execute = true;
                $interfaceData->data = $jwtObject->getData();
                //token控制
                $id = $interfaceData->data["objectInfo"]["id"];
                $jti = $jwtObject->getJti();
                $tokenBool = _sealRedis(function (\EasySwoole\Redis\Redis $redis) use ($token_tag, $id, $jti){
                    $rJtiS = json_decode($redis->hGet("token-{$token_tag}", $id), 1);
                    $returnBool = false;
                    if (empty($rJtiS)){return $returnBool;}
                    foreach ($rJtiS as $rjti){
                        if ($rjti == $jti){$returnBool = true;break;}
                    }
                    return $returnBool;
                });
                if ($tokenBool !== true){
                    $interfaceData->execute = false;
                    $interfaceData->message = "token expired";
                }
                break;
            case  -1:
                $interfaceData->message = "invalid token";  break;
            case  -2:
                $interfaceData->message = "overdue token";  break;
        }
        return $interfaceData;
    }catch (\EasySwoole\Jwt\Exception $e) {
        throw new \Exception("decode token error !");
    }
}

/** 请求是否微信打开
 * @param string $userAgent
 * @return bool
 * @author 韩天尊
 * @date 2022-03-21 17:43
 */
function requestIsWeiXin(string $userAgent): bool
{
    //$userAgent = $this->request()->getHeader("user-agent")[0];
    if (strpos($userAgent, 'MicroMessenger') == false && strpos($userAgent, 'Windows Phone') == false) {
        return false;//普通浏览器打开
    } else {
        return true;//微信打开
    }
}

/** 操作系统
 * @param string $userAgent
 * @return string[]
 * @author 韩天尊
 * @date 2022-03-21 17:43
 */
function getRequestSystem(string $userAgent): array
{	// 获取操作系统版本
    //$userAgent = $this->request()->getHeader("user-agent")[0];
    $agent = $userAgent;
    $platform = '';
    $version = '';
    if (preg_match ( '/win/i', $agent )) {
        $platform = 'Win';
        if (preg_match ( '/nt 10/i', $agent )) {
            $version = '10';
        } else if (preg_match ( '/nt 6.3/i', $agent )) {
            $version = '8.1';
        } else if (preg_match ( '/nt 6.2/i', $agent )) {
            $version = '8';
        } else if (preg_match ( '/nt 6.1/i', $agent )) {
            $version = '7';
        } else if (preg_match ( '/nt 6.0/i', $agent )) {
            $version = 'Vista';
        } else if (preg_match ( '/nt 5.2/i', $agent )) {
            $version = '2003';
        } else if (preg_match ( '/nt 5.1/i', $agent )) {
            $version = 'XP';
        } else if (preg_match ( '/nt 5.0/i', $agent )) {
            $version = '2000';
        } else if (preg_match ( '/nt/i', $agent )) {
            $version = 'NT';
        } else if (preg_match ( '/32/i', $agent )) {
            $version = '32';
        } else if (strpos ( $agent, '95' )) {
            $version = '95';
        } else if (preg_match ( '/win 9x/i', $agent ) && strpos ( $agent, '4.90' )) {
            $version = 'ME';
        } else if (preg_match ( '/98/i', $agent )) {
            $version = '98';
        }
    }
    else if (preg_match ( '/MicroMessenger/i', $agent )) {
        $platform = 'Program';//小程序
    }
    else if (preg_match ( '/Android/i', $agent )) {
        $platform = 'Android';
    } else if (preg_match ( '/iPhone/i', $agent )) {
        $platform = 'iPhone';
    } else if (preg_match ( '/linux/i', $agent )) {
        $platform = 'Linux';
    } else if (preg_match ( '/unix/i', $agent )) {
        $platform = 'Unix';
    } else if (preg_match ( '/sun/i', $agent ) && preg_match ( '/os/i', $agent )) {
        $platform = 'SunOS';
    } else if (preg_match ( '/ibm/i', $agent ) && preg_match ( '/os/i', $agent )) {
        $platform = 'IBM OS/2';
    } else if (strpos ( $agent, 'Macintosh' )) {
        $platform = 'Mac';
    } else if (preg_match ( '/PowerPC/i', $agent )) {
        $platform = 'PowerPC';
    } else if (preg_match ( '/AIX/i', $agent )) {
        $platform = 'AIX';
    } else if (preg_match ( '/HPUX/i', $agent )) {
        $platform = 'HPUX';
    } else if (preg_match ( '/NetBSD/i', $agent )) {
        $platform = 'NetBSD';
    } else if (preg_match ( '/BSD/i', $agent )) {
        $platform = 'BSD';
    } else if (preg_match ( '/OSF1/i', $agent )) {
        $platform = 'OSF1';
    } else if (preg_match ( '/IRIX/i', $agent )) {
        $platform = 'IRIX';
    } else if (preg_match ( '/FreeBSD/i', $agent )) {
        $platform = 'FreeBSD';
    } else if (preg_match ( '/teleport/i', $agent )) {
        $platform = 'teleport';
    } else if (preg_match ( '/flashget/i', $agent )) {
        $platform = 'flashget';
    } else if (preg_match ( '/webzip/i', $agent )) {
        $platform = 'webzip';
    } else if (preg_match ( '/offline/i', $agent )) {
        $platform = 'offline';
    }
    $arr = array (
        'system' => $platform,//系统
        'version' => $version//版本
    );
    return $arr;
}

/** 判断当前时间是否在某个区间内
 * @param $time
 * @param $timeBetween
 * @return bool
 */
function sectionTime($time, $timeBetween): bool
{
//    $time = time();
//    $timeBetween=['2018-01-20 01:00:00','2018-01-20 02:30:00'];
//    $xxx = sectionTime($time, $timeBetween);
    $timeBegin1 = strtotime($timeBetween[0]);
    $timeEnd1 = strtotime($timeBetween[1]);
    if($time > $timeBegin1 && $time < $timeEnd1){
        return true;
    }else{
        return false;
    }
}
/** 计算两个时间差
 * @param $begin_time
 * @param $end_time
 * @param false $timestamp
 * @return array
 */
function timediff($begin_time, $end_time, $timestamp=false)
{
//    $startdate="11:40:00";
//    $enddate="24:45:09";
//    timediff($startdate, $enddate);

//    $startdate=time();
//    $enddate=$startdate+50000;
//    timediff($startdate, $enddate, true);

    if ($timestamp == true){
        $starttime = $begin_time;
        $endtime = $end_time;
    }else{
        $starttime = strtotime($begin_time);
        $endtime = strtotime($end_time);
    }
    if ($starttime > $endtime){
        return [];//如果开始时间大于结束时间则返回空
    }
    $timediff = $endtime-$starttime;
    $days = intval($timediff/86400);
    $remain = $timediff%86400;
    $hours = intval($remain/3600);
    $remain = $remain%3600;
    $mins = intval($remain/60);
    $secs = $remain%60;
    $res = array("day" => $days,"hour" => $hours,"min" => $mins,"sec" => $secs, "timediff"=>$timediff);
    return $res;
}

/** 自定义 http Exception
 * @param int $code
 * @param string $message
 * @param array $data
 * @throws CustomHttpException
 * *    author 韩天尊
 */
function customHttpException(int $code=400, string $message="", array $data=[]){
    $customHttpException = new CustomHttpException();
    $customHttpException->setCode($code)->setMessage($message)->setData($data);
    /** @var TYPE_NAME $customHttpException */
    throw $customHttpException;
}

/** 数据库的all方法obj快速转换ary
 * @param array $array
 * @return array
 * *    author 韩天尊
 */
function dbObjectToArray(array $array): array
{
    foreach ($array as &$item){
        $item = $item->toArray();
    }
    return $array;
}

/** 一维数组冒泡排序
 * @param array $array
 * @param int $sortType
 * @return array
 * *    author 韩天尊
 */
function bubble(array $array, int $sortType=0): array
{
    $arrayLength = count($array)-1;
    for ($i=0; $i<$arrayLength; $i++){
        for($j=0; $j<$arrayLength-$i; $j++){
            if (($sortType===0)? ($array[$j] > $array[$j+1]) : ($array[$j] < $array[$j+1])){
                $container = $array[$j];
                $array[$j] = $array[$j+1];
                $array[$j+1] = $container;
            }
        }
    }
    return $array;
}

